Suspense for Data Fetching
dataのfetch中にfallbackで指定したものを表示する
Componentがloading中の表示の責務を負わなくなった
fetchしたデータを返す関数の返り値が、T|undefinedからTになった
これは、try..catchの文脈で内部でthrowする関数の返り値がT|undefinedからTになったのと同じmrsekut.icon
suspendする操作は、基本的にはlibraryやframeworkがやってくれる
そのため、自分でpromiseをthrowすることは稀なはずmrsekut.icon
使用時に気にする点
どのlibraryやframeworkがsuspenseに対応しているか
それらはisLoadingのような値を返す代わりに、内部でpromiseをthrowする
仕様を確認すべきだが対応していない場合は、|undefinedの型になるはずなのでこれは気づけるはずmrsekut.icon
try..catchと同じで、どれがthrowするのかを型で判別できない
内部でthrowしている可能性も踏まえて囲んでいかないといけない
使用例
登場人物は3つ
内部でpromiseをthrowするfetch機能
fetchしたデータを使用する子Component
fallbackなどを制御する親Component
fetch機能と、それを使用する子Component
code:ts
const Child: React.FC = () => {
const data = useData(); // data :: string
return <div>Data is {data}</div>;
};
useData()は、内部でdataをfetchしてそれを返す
これが、suspenseに対応した実装になっているものとする
つまり、dataのfetch中に、promiseをthrowする実装になっている
これは自作しても良いし、React v18に対応したlibraryなら勝手にそうなっている
着目すべき点は、
useData()の返り値は、string|undefinedではなく、stringである
Childの中ではif(data==null) {..}の処理がない
データが存在する場合のViewの表示のみが責務とできる
fallbackなどを制御する親Component
Childの準備がまだできていない時はloading..を表示する
code:ts
<Suspense fallback={<div>loading..</div>}>
<Child />
</Suspense>
従来はどうしていたか
fetchしてきたデータをstateに入れたり、
ReduxでREQUEST, SUCCESS, FAILUREのように3つ定義していたが、それも不要になった
code:ts
const Child: React.FC = () => {
const data = useData(); // data :: string|undefined
if(data == null) return null;
return <div>Data is {data}</div>;
};
UIに合わせて適当に変えればいい
例 1つだけ囲う
C内のsuspendは、A,Bには影響しない
例えば、Cのloadよりも、Aの方が速く終わるなら、Aが先に表示される
code:tsx
<A/>
<B/>
<Suspense>
<C>
</Suspense>
例 A,B,C,Dのいずれかがsuspendした時は、全てを1まとまりしてfallbackする
code:tsx
<Suspense fallback="..">
<A/>
<B/>
<C/>
</Suspense>
この時、Aがsuspendを引き起こした時、BもCも再renderingされる
個々にloadingを表示するのかや、どれを同じタイミングで表示すべきなのかなどを考慮する
参考
雑に利用するだけならこれ読めば十分
自分でthrowしてみたりして挙動を確認するハンズオン
React v17時のdocs
<Suspense>を囲う場所を変更する
ちゃんと理解してないmrsekut.icon
親の親とかをかこったりしたらなおった
なんで?
なんか条件がありそう
Componentの中でswitch的な分岐してるとか
投げられたpromiseがsettledになることで再renderingする そのpromiseがfulfilledではなく、rejectされたらどうなる #?? errorがthrowされる
Suspenese内の処理でerrorが起きた時にどうするか
code:ts
<ErrorBoundary fallback={<h2>Could not fetch posts.</h2>}>
<Suspense fallback={<h1>Loading posts...</h1>}>
<ProfileTimeline />
</Suspense>
</ErrorBoundary>
suspendされた時は、renderingが成功していない
だから、useStateを使った状態のsetとかも破棄される
従来のuseEffectなどを使ったdata fetchと比較すると並行性が上がっている
ツリーの形式で、fetchとrenderingをやると、
親のfetchが終わった後に、子をrenderingして、その中で更にfetchとなる
この時、後者の方のfetchをスタートするタイミングが、親のfetchの終わりのタイミングに依存してしまう
親の方に時間がかかる場合、子の方がかなり遅くなる
suspenseを使った場合は、とりあえずみんなfetchし始めた状態で、終わったものからrenderingするといった事ができるようになる
課題
https://youtu.be/vv2Qx1QLEZs?t=648
waterfall問題
2つのリクエストを同じcomopnentでやると直列になってしまう
useで解決される?
fetch on render 問題
親のrenderingが終わってないと子のfetchが始まらない